Skip to content

Labb3 java fx martin stenhagen#26

Closed
MartinStenhagen wants to merge 24 commits intomainfrom
labb3JavaFX_Martin-Stenhagen
Closed

Labb3 java fx martin stenhagen#26
MartinStenhagen wants to merge 24 commits intomainfrom
labb3JavaFX_Martin-Stenhagen

Conversation

@MartinStenhagen
Copy link

@MartinStenhagen MartinStenhagen commented Nov 14, 2025

Summary by CodeRabbit

  • New Features

    • Chat messaging with timestamped bubbles, enter-to-send and auto-scroll
    • Image upload, inline display and clickable enlarged preview
    • Real-time status indicators for send/upload operations
    • Redesigned chat layout and visual stylesheet for message bubbles and controls
  • Tests

    • New automated tests covering message send/receive and image upload flows

@coderabbitai
Copy link

coderabbitai bot commented Nov 14, 2025

Walkthrough

The PR converts a simple JavaFX demo into a chat client: adds Ntfy messaging integration, a local HTTP image server, a stateful HelloModel, UI/controller changes for chat bubbles and image handling, new DTOs and connection implementations, tests and test doubles, updated module and Maven configuration, and new UI assets (FXML/CSS).

Changes

Cohort / File(s) Summary
Configuration & Build
\.gitignore`, `pom.xml`, `HelloFX.iml`, src/main/java/module-info.java`
Added .env to .gitignore; added IntelliJ module file; renamed artifact to HelloFX; updated dependencies (dotenv, jackson, wiremock, javafx modules) and test/plugin configuration; updated module name and required modules/opens.
Core Messaging Model & DTOs
src/main/java/com/example/HelloModel.java, src/main/java/com/example/NtfyConnection.java, src/main/java/com/example/NtfyConnectionImpl.java, src/main/java/com/example/NtfyMessageDto.java
New HelloModel with observable messages, sendMessage/sendImage, receiveMessages; added NtfyConnection interface; implemented NtfyConnectionImpl with HTTP send/receive background thread; added NtfyMessageDto record and Attachment.
Image Server
src/main/java/com/example/ImageServer.java
New embedded HTTP server with POST /upload (stores files, returns public URL) and GET /images/{filename} (serves files); start/stop lifecycle.
Application & UI Controller
src/main/java/com/example/HelloFX.java, src/main/java/com/example/HelloController.java
HelloFX loads .env, initializes NtfyConnectionImpl and ImageServer, injects connection into controller, manages shutdown; HelloController accepts model/connection, wires UI controls, renders text/image bubbles, handles send/send-image actions, and supports image enlargement and status updates.
UI Layout & Styling
src/main/resources/com/example/hello-view.fxml, src/main/resources/com/example/chat-style.css
Replaced StackPane with BorderPane and full chat layout (ScrollPane/VBox messages, HBox bottom bar with TextField, image/send buttons, status Label); added stylesheet defining sent/received bubble styles, images, timestamps, and controls.
Tests & Test Doubles
src/test/java/com/example/HelloModelTest.java, src/test/java/com/example/NtfyConnectionSpy.java
Added HelloModelTest with unit/integration scenarios using WireMock; added NtfyConnectionSpy test double to record sends and simulate incoming messages.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as HelloController
    participant Model as HelloModel
    participant Conn as NtfyConnection
    participant ImgServer as ImageServer
    participant NtfyServer as Ntfy Server

    rect rgb(200,220,255)
    Note over User,UI: Sending a message/image
    User->>UI: Enter text / choose image, click Send
    UI->>Model: sendMessage(text) / sendImage(file)
    alt image
      Model->>ImgServer: POST /upload (file)
      ImgServer-->>Model: 200 + http://localhost:8081/images/UUID.ext
    end
    Model->>Conn: send(JSON payload with message or image URL)
    Conn->>NtfyServer: POST /<topic>
    end

    rect rgb(220,255,220)
    Note over NtfyServer,Conn: Receiving messages (background)
    Conn->>NtfyServer: GET /<topic>/json (poll/SSE)
    NtfyServer-->>Conn: stream NtfyMessageDto events
    Conn->>Model: callback with NtfyMessageDto
    Model->>UI: ObservableList.add(message)
    UI->>UI: render bubble or image, scroll to bottom
    UI-->>User: message displayed
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Pay special attention to:
    • NtfyConnectionImpl.receive() — background thread, retry/backoff, JSON/SSE parsing, and termination logic.
    • ImageServer — path normalization, content-type mapping, file write/read safety.
    • HelloModel.sendImage() and uploadToLocalServer() — multipart/form-data handling and error paths.
    • HelloController UI-thread handling — Platform.runLater usage, scrolling, and image-popup lifecycle.
    • pom.xml/module-info changes — module requirements and plugin configuration compatibility.

Poem

🐰
I dug a tunnel through the code so bright,
Messages hop in bubbles, green and white.
Images uploaded, then shown large and clear,
Threads hum softly, bringing friends near.
A little rabbit cheers — the chat is here!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Labb3 java fx martin stenhagen' is vague and uses non-descriptive terms that don't convey meaningful information about the changeset. Replace the title with a concise, clear summary of the main change, such as 'Implement JavaFX chat application with Ntfy integration and local image server' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (12)
src/test/java/com/example/FxTestHelper.java (2)

4-6: Consider actual toolkit initialization if needed.

The initToolkit() method is currently a no-op. If JavaFX toolkit initialization is actually required for tests, consider using Platform.startup(() -> {}) or similar. If no initialization is needed, the method name may be misleading.


5-5: Use English for code comments.

The comment is in Swedish. For international collaboration and consistency, use English throughout the codebase.

Apply this diff:

-        // Ingen GUI krävs för tester
+        // No GUI required for tests
src/main/java/com/example/HelloFX.java (1)

20-23: Improve error handling and formatting.

Two issues: (1) Line 20 has inconsistent indentation with extra spaces, and (2) printStackTrace() on line 22 is not suitable for production—use proper logging instead.

Apply this diff:

         new Thread(() -> {
             try {
-                    new ImageServer(8081);
-                }   catch (IOException e) {
-                    e.printStackTrace();
-                }
+                new ImageServer(8081);
+            } catch (IOException e) {
+                System.err.println("Failed to start ImageServer: " + e.getMessage());
+            }
src/main/java/com/example/ImageServer.java (1)

32-32: Avoid hardcoded localhost URL.

The hardcoded localhost URL on line 32 won't work when accessed from other machines. Consider making the hostname configurable or using the request's Host header.

Example approach:

-                String imageUrl = "http://localhost:" + port + "/images/" + filename;
+                String host = exchange.getRequestHeaders().getFirst("Host");
+                String imageUrl = "http://" + (host != null ? host : "localhost:" + port) + "/images/" + filename;
pom.xml (1)

64-69: Consider upgrading to WireMock 3.13.1 (stable release) or await 4.x stability.

The latest stable version of WireMock is 3.13.1, whereas the project currently uses the beta 4.0.0-beta.15. Migrating to the stable 3.13.1 release eliminates beta stability risks, though it involves a major version downgrade from 4.x to 3.x. Alternatively, monitor the 4.x release cycle for a stable 4.x version to avoid regression in features or improvements.

src/main/java/com/example/NtfyConnection.java (1)

6-23: Clarify sendImage and receive contracts in the interface

The abstraction looks good, but the semantics of sendImage(File imageFile, String clientId) and the lifecycle of receive(Consumer<NtfyMessageDto> consumer) vs stopReceiving() are implicit. Consider adding brief Javadoc for:

  • What clientId represents and when sendImage returns false.
  • Whether receive may be called multiple times and what happens if it is called after stopReceiving().
src/main/java/com/example/HelloController.java (2)

68-74: Extract "MartinsTopic" to a shared constant

Using the hard‑coded topic string "MartinsTopic" here to determine sentByUser couples the UI to a magic string that’s also used elsewhere (e.g., in NtfyConnectionSpy and tests). To avoid divergence, consider extracting this topic name into a shared constant (e.g., in HelloModel or a config class) and reusing it.


40-48: Prefer passing an owner window to the FileChooser

fileChooser.showOpenDialog(null) works but creates a dialog without a clear owner, which can cause focus/stacking quirks on some platforms. Consider passing the current window, for example:

var window = messageField.getScene() != null ? messageField.getScene().getWindow() : null;
File imageFile = fileChooser.showOpenDialog(window);
src/test/java/com/example/HelloModelTest.java (1)

24-110: Good coverage of model and HTTP integration; consider tightening assertions later

The tests nicely cover:

  • Delegation to NtfyConnection via the spy.
  • That incoming messages are stored in the model.
  • Real HTTP interactions with NtfyConnectionImpl using WireMock, including image upload flow.

As a future enhancement, you might consider asserting full JSON payloads (instead of regex matching(...)) to make the WireMock expectations more explicit, but this is optional given the current scope.

src/main/java/com/example/mvnw.cmd (1)

1-189: Wrapper script looks standard, but the location is unconventional

This is the stock Maven wrapper script and is fine as-is, but placing mvnw.cmd under src/main/java/com/example is unusual — wrapper launchers are normally at the project root so developers can run ./mvnw from there and tooling can detect them. Consider moving mvnw.cmd (and its companion files) to the repository root to align with typical Maven setups and avoid confusion.

src/main/java/com/example/mvnw (1)

1-295: Unix mvnw script is fine, but should probably live at project root

The script content matches the standard Maven Wrapper (3.3.4) and is OK, but having src/main/java/com/example/mvnw instead of a root-level mvnw is non-standard and makes it harder to discover and use (./mvnw from project root won’t hit this file). I’d recommend relocating this file (and its .mvn/wrapper directory) to the repository root so Maven tooling and developers can use the wrapper in the usual way.

src/main/java/com/example/HelloModel.java (1)

116-127: Tighten resource handling and error checks in uploadToLocalServer

uploadToLocalServer writes the file and then reads the response bytes directly from conn.getInputStream() without closing that stream explicitly or checking the HTTP status code. While disconnect() will eventually clean up, a small adjustment makes it safer and clearer:

-        try (OutputStream os = conn.getOutputStream()) {
-            Files.copy(imageFile.toPath(), os);
-        }
-        String imageUrl = new String(conn.getInputStream().readAllBytes());
-        conn.disconnect();
-        return imageUrl;
+        try (OutputStream os = conn.getOutputStream()) {
+            Files.copy(imageFile.toPath(), os);
+        }
+        int status = conn.getResponseCode();
+        if (status < 200 || status >= 300) {
+            throw new IOException("Upload failed with HTTP " + status);
+        }
+        try (InputStream in = conn.getInputStream()) {
+            String imageUrl = new String(in.readAllBytes());
+            return imageUrl;
+        } finally {
+            conn.disconnect();
+        }

This ensures both streams are closed, and callers get a clear failure signal when the upload endpoint responds with an error.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c51e8 and 2f96c91.

📒 Files selected for processing (19)
  • .gitignore (1 hunks)
  • HelloFX.iml (1 hunks)
  • pom.xml (4 hunks)
  • src/main/java/com/example/.mvn/wrapper/maven-wrapper.properties (1 hunks)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (1 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/main/java/com/example/ImageServer.java (1 hunks)
  • src/main/java/com/example/NtfyConnection.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/main/java/com/example/NtfyMessageDto.java (1 hunks)
  • src/main/java/com/example/mvnw (1 hunks)
  • src/main/java/com/example/mvnw.cmd (1 hunks)
  • src/main/java/module-info.java (1 hunks)
  • src/main/resources/com/example/chat-style.css (1 hunks)
  • src/main/resources/com/example/hello-view.fxml (1 hunks)
  • src/test/java/com/example/FxTestHelper.java (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/module-info.java (1)
src/main/java/com/example/HelloFX.java (1)
  • HelloFX (12-43)
src/test/java/com/example/HelloModelTest.java (1)
src/test/java/com/example/FxTestHelper.java (1)
  • FxTestHelper (3-7)
🪛 Biome (2.1.2)
src/main/resources/com/example/chat-style.css

[error] 2-2: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 3-3: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 7-7: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 8-8: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 9-9: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 10-10: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 11-11: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 12-12: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 13-13: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 14-14: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 17-17: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 17-17: Unexpected unknown function: dropshadow

Use a known function instead.
See MDN web docs for more details.

(lint/correctness/noUnknownFunction)


[error] 18-18: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 23-23: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 24-24: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 25-25: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 26-26: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 27-27: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 28-28: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 29-29: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 30-30: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 34-34: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 38-38: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

🔇 Additional comments (7)
.gitignore (1)

3-3: LGTM!

Adding .env to .gitignore is a best practice to prevent committing environment variables and secrets to version control.

HelloFX.iml (1)

1-9: LGTM!

Standard IntelliJ IDEA module configuration with appropriate source folder declarations.

src/main/java/com/example/.mvn/wrapper/maven-wrapper.properties (1)

1-3: LGTM!

Standard Maven Wrapper configuration with appropriate distribution settings.

src/main/resources/com/example/chat-style.css (1)

1-39: LGTM! Static analysis warnings are false positives.

The static analysis tool (Biome) is flagging JavaFX-specific CSS properties as unknown. This is expected—JavaFX CSS uses the -fx- prefix and includes properties like -fx-background-color, -fx-background-radius, etc., which are valid in JavaFX but not standard CSS. The stylesheet is correctly formatted for JavaFX.

pom.xml (1)

50-63: I need to verify jackson-annotations as well and confirm these are the latest versions. Let me search for additional information.

Based on the verification results:

No current security vulnerabilities found, but consider updating jackson libraries to the latest 2.x versions.

jackson-core 2.17.2 has no direct vulnerabilities, and the security advisory check confirmed no matching vulnerabilities for jackson-databind 2.17.2 (all reported CVEs affect older versions like 2.9.x, 2.12.x, and 2.13.x ranges). dotenv-java 3.2.0 also shows no known security advisories.

However, the latest version of jackson-databind is 2.20.1, and your project uses 2.17.2—four minor versions behind. Jackson core components (annotations, streaming, databind) must have the same minor version, so consider upgrading both jackson-databind and jackson-annotations from 2.17.2 to 2.20.1 to stay current with the latest patches and improvements.

src/main/java/module-info.java (1)

1-13: Module dependencies look consistent; verify module name usage

The added requires and the opens com.example to javafx.fxml, com.fasterxml.jackson.databind; line align with JavaFX + Jackson usage. Please just double‑check that:

  • The module name HelloFX matches your build/runtime configuration (e.g., module option, IDE run config).
  • No old references to a previous module name remain (if it was renamed).
src/main/resources/com/example/hello-view.fxml (1)

10-31: FXML wiring matches the controller and looks consistent

The ids (messageField, chatBox, chatScroll, statusLabel) and action handlers (#handleSend, #handleSendImage) line up with HelloController, and the layout is clean for a chat UI.

Comment on lines +26 to +38
server.createContext("/upload", exchange -> {
if ("POST".equals(exchange.getRequestMethod())) {
String filename = UUID.randomUUID() + ".jpg";
Path filePath = imageDir.resolve(filename);
Files.copy(exchange.getRequestBody(), filePath, StandardCopyOption.REPLACE_EXISTING);

String imageUrl = "http://localhost:" + port + "/images/" + filename;
byte[] response = imageUrl.getBytes();
exchange.sendResponseHeaders(200, response.length);
exchange.getResponseBody().write(response);
}
exchange.close();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add upload validation and size limits.

The upload endpoint has several security and quality issues:

  1. No file size limit—an attacker could fill disk space
  2. No content type validation—any data is accepted as an image
  3. All files saved as .jpg regardless of actual format (line 28)
  4. No authentication/authorization

Consider implementing:

  • Maximum file size check before accepting upload
  • Content type validation (check headers and/or magic bytes)
  • Determine actual file extension from content type
  • Basic authentication or token-based authorization

⚠️ Potential issue | 🟠 Major

Ensure exchange is closed in all cases.

The exchange.close() on line 37 should be in a finally block to ensure it executes even if an exception occurs during request processing.

Apply this diff:

         server.createContext("/upload", exchange -> {
-            if ("POST".equals(exchange.getRequestMethod())) {
-                String filename = UUID.randomUUID() + ".jpg";
-                Path filePath = imageDir.resolve(filename);
-                Files.copy(exchange.getRequestBody(), filePath, StandardCopyOption.REPLACE_EXISTING);
-
-                String imageUrl = "http://localhost:" + port + "/images/" + filename;
-                byte[] response = imageUrl.getBytes();
-                exchange.sendResponseHeaders(200, response.length);
-                exchange.getResponseBody().write(response);
+            try {
+                if ("POST".equals(exchange.getRequestMethod())) {
+                    String filename = UUID.randomUUID() + ".jpg";
+                    Path filePath = imageDir.resolve(filename);
+                    Files.copy(exchange.getRequestBody(), filePath, StandardCopyOption.REPLACE_EXISTING);
+
+                    String imageUrl = "http://localhost:" + port + "/images/" + filename;
+                    byte[] response = imageUrl.getBytes();
+                    exchange.sendResponseHeaders(200, response.length);
+                    exchange.getResponseBody().write(response);
+                }
+            } finally {
+                exchange.close();
             }
-            exchange.close();

Apply the same pattern to the /images endpoint (lines 41-50).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
server.createContext("/upload", exchange -> {
if ("POST".equals(exchange.getRequestMethod())) {
String filename = UUID.randomUUID() + ".jpg";
Path filePath = imageDir.resolve(filename);
Files.copy(exchange.getRequestBody(), filePath, StandardCopyOption.REPLACE_EXISTING);
String imageUrl = "http://localhost:" + port + "/images/" + filename;
byte[] response = imageUrl.getBytes();
exchange.sendResponseHeaders(200, response.length);
exchange.getResponseBody().write(response);
}
exchange.close();
});
server.createContext("/upload", exchange -> {
try {
if ("POST".equals(exchange.getRequestMethod())) {
String filename = UUID.randomUUID() + ".jpg";
Path filePath = imageDir.resolve(filename);
Files.copy(exchange.getRequestBody(), filePath, StandardCopyOption.REPLACE_EXISTING);
String imageUrl = "http://localhost:" + port + "/images/" + filename;
byte[] response = imageUrl.getBytes();
exchange.sendResponseHeaders(200, response.length);
exchange.getResponseBody().write(response);
}
} finally {
exchange.close();
}
});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/main/java/com/example/ImageServer.java (1)

34-57: Upload endpoint still lacks size/type validation—consider hardening it.

The /upload handler still accepts arbitrary bytes with no file size limit or content‑type check. For anything beyond a trusted local lab setup, you should add basic validation (e.g., reject bodies over some MB threshold, check Content-Type and/or magic bytes, and avoid always forcing the .jpg extension) to reduce abuse and disk‑fill risk.

🧹 Nitpick comments (5)
src/test/java/com/example/NtfyConnectionSpy.java (3)

24-29: Consider making the return value configurable for failure testing.

The method always returns true, preventing tests from verifying error-handling paths in HelloModel.sendImage(). For more comprehensive testing, consider adding a field like boolean simulateImageUploadFailure to toggle the return value.

Example enhancement:

 public class NtfyConnectionSpy implements NtfyConnection {
     public String lastSentMessage;
     public File lastSentImage;
     public String lastClientId;
     public Consumer<NtfyMessageDto> messageHandler;
+    public boolean simulateImageUploadFailure = false;

     @Override
     public boolean sendImage(File imageFile, String clientId) {
         this.lastSentImage = imageFile;
         this.lastClientId = clientId;
         System.out.println("🧪 Spy sendImage(): " + imageFile.getName() + " | clientId=" + clientId);
-        return true;
+        return !simulateImageUploadFailure;
     }

37-38: Consider clearing the handler and adding debug output.

The empty stopReceiving() implementation leaves messageHandler active, allowing simulateIncomingMessage() to deliver messages even after stopping. For clearer test semantics, consider clearing the handler.

Apply this diff:

 @Override
-public void stopReceiving() { }
+public void stopReceiving() {
+    this.messageHandler = null;
+    System.out.println("🧪 Spy stopReceiving() called");
+}

43-58: Good fix on time units; consider adding null safety.

Line 45 correctly uses System.currentTimeMillis() / 1000 to provide epoch seconds, resolving the past review concern.

However, the method lacks a null check on the json parameter, which could cause a NullPointerException on line 52 or 54.

Apply this diff to add null safety:

 public void simulateIncomingMessage(String json) {
+    if (json == null) {
+        throw new IllegalArgumentException("json cannot be null");
+    }
     if (messageHandler != null) {
         long now = System.currentTimeMillis() / 1000;

Note: The regex-based extraction remains acceptable for simple test payloads, as previously discussed.

src/main/java/com/example/HelloModel.java (1)

129-149: Consider adding HTTP timeouts for uploadToLocalServer to avoid indefinite blocking.

Right now the upload uses a raw HttpURLConnection without connect/read timeouts, so a hung or unreachable image server can block the caller indefinitely. Adding reasonable timeouts would make failures predictable:

-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setRequestMethod("POST");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setRequestMethod("POST");
+        conn.setConnectTimeout(5_000);
+        conn.setReadTimeout(10_000);
src/main/java/com/example/HelloController.java (1)

42-58: Verify that topic is a reliable indicator of “sent by user”.

sentByUser is computed as msg.topic().equals(HelloModel.DEFAULT_TOPIC). If all chat participants publish to the same Ntfy topic, remote messages will likely have the same topic and be rendered as if they were sent by this client (right‑aligned). Consider basing this on a per‑client identifier (e.g. clientId embedded in the payload) instead, or otherwise confirm that your connection layer sets distinct topics for local vs remote messages.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f96c91 and 6ade543.

📒 Files selected for processing (10)
  • pom.xml (3 hunks)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (1 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/main/java/com/example/ImageServer.java (1 hunks)
  • src/main/java/com/example/NtfyConnection.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/main/resources/com/example/chat-style.css (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/example/NtfyConnection.java
  • src/test/java/com/example/HelloModelTest.java
  • src/main/java/com/example/NtfyConnectionImpl.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/example/NtfyConnectionSpy.java (1)
src/main/java/com/example/HelloModel.java (1)
  • HelloModel (19-153)
src/main/java/com/example/HelloController.java (1)
src/main/java/com/example/HelloModel.java (1)
  • HelloModel (19-153)
🪛 Biome (2.1.2)
src/main/resources/com/example/chat-style.css

[error] 2-2: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 3-3: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 7-7: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 8-8: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 9-9: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 10-10: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 11-11: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 12-12: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 13-13: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 14-14: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 18-18: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 19-19: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 20-20: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 21-21: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 22-22: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 23-23: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 24-24: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 25-25: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 29-29: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 30-30: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 34-34: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 35-35: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 40-40: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 40-40: Unexpected unknown function: dropshadow

Use a known function instead.
See MDN web docs for more details.

(lint/correctness/noUnknownFunction)


[error] 41-41: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 42-42: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 43-43: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 44-44: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 48-48: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 49-49: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 53-53: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 57-57: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 61-61: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 62-62: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 63-63: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

🔇 Additional comments (8)
src/main/resources/com/example/chat-style.css (1)

1-64: Well-designed chat UI stylesheet; static analysis flags are false positives.

This CSS file provides solid styling for the chat application with a cohesive color scheme (green for sent messages, white/gray for received), appropriate contrast, and well-structured layout. All properties use valid JavaFX CSS syntax with the -fx- prefix.

The 36 errors from Biome are false positives—Biome is a web CSS linter and doesn't recognize JavaFX-specific CSS properties and functions like -fx-effect with dropshadow(). These are all valid JavaFX CSS and will work correctly.

src/test/java/com/example/NtfyConnectionSpy.java (3)

10-15: LGTM! Appropriate test spy pattern.

The public mutable fields are acceptable for test doubles, allowing tests to inspect recorded values and verify behavior.


17-21: LGTM! Clean spy implementation.

The method correctly records the sent message for test verification and includes helpful debug output.


31-35: LGTM! Handler registration works correctly.

The method properly stores the message handler for use in simulateIncomingMessage().

pom.xml (1)

8-18: POM dependency and plugin setup looks consistent; just verify external versions.

The earlier JUnit and javafx-swing version inconsistencies are now resolved, and the new deps/plugins (JavaFX controls/FXML/Swing, dotenv, Jackson, WireMock, surefire with useModulePath=false) look coherent for this module-based JavaFX app. Please just double‑check that the chosen versions (notably javafx.version = 25 and the Jackson/WireMock versions) exist in your target repo and work with your JDK/toolchain.

Also applies to: 21-76, 78-108

src/main/java/com/example/HelloModel.java (1)

20-27: Messaging flow and FX-thread safety look solid now.

The model wiring to NtfyConnection, the Markdown image detection using substring/indexes, and the updated addMessageSafely that marshals list updates onto the FX Application Thread together address the earlier regex and threading concerns. The ObservableList should now be safe to bind/listen to from the UI.

Also applies to: 47-77, 84-127

src/main/java/com/example/HelloFX.java (1)

12-61: Single connection + ImageServer lifecycle wiring looks good.

Using one NtfyConnectionImpl instance injected into HelloController, starting ImageServer on a daemon thread, and stopping both in the stage.setOnCloseRequest handler provides a clean lifecycle and resolves the earlier duplicate‑connection/shutdown concerns.

src/main/java/com/example/ImageServer.java (1)

17-23: HTTP server structure, path normalization, and exchange closing look correct.

imageDir is normalized, both /upload and /images handlers validate that resolved paths stay under this directory, and exchange.close() is now in finally blocks. This addresses the earlier path traversal and resource‑cleanup concerns and gives you a robust baseline image server for the chat.

Also applies to: 28-69, 71-108, 115-120

Comment on lines +6 to +9
/**
* Test-double for HelloModel.
* Sends and receive messages without actual HTTP.
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor typo in documentation.

Line 8 has "Sends and receive messages" but should be "Sends and receives messages" for grammatical correctness.

Apply this diff:

 /**
  * Test-double for HelloModel.
- * Sends and receive messages without actual HTTP.
+ * Sends and receives messages without actual HTTP.
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Test-double for HelloModel.
* Sends and receive messages without actual HTTP.
*/
/**
* Test-double for HelloModel.
* Sends and receives messages without actual HTTP.
*/
🤖 Prompt for AI Agents
In src/test/java/com/example/NtfyConnectionSpy.java around lines 6 to 9, the
Javadoc comment has a minor grammatical typo: change "Sends and receive messages
without actual HTTP." to "Sends and receives messages without actual HTTP." so
the verb agrees with the subject; update the comment accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/main/java/com/example/HelloController.java (1)

43-61: Platform.runLater wrapper still present around Change iteration.

Past review comments correctly identified that ListChangeListener.Change should be processed synchronously within the callback, not wrapped in Platform.runLater(), as the Change object is only valid during the notification. However, since HelloModel.addMessageSafely() (lines 140-148 in HelloModel.java) already marshals updates to the FX thread when needed, the listener should execute on the FX thread. Remove the Platform.runLater() wrapper and process the change directly.

🧹 Nitpick comments (2)
src/main/java/com/example/HelloModel.java (1)

81-105: Consider using a logger instead of printStackTrace.

The image sending logic is solid, but printStackTrace() at line 102 should ideally be replaced with proper logging (e.g., java.util.logging.Logger or SLF4J) for production code.

src/main/java/com/example/NtfyConnectionImpl.java (1)

29-42: Consider proper logging framework.

The send implementation is correct, but printStackTrace() at line 40 should be replaced with a proper logging framework for better observability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ade543 and df06cdd.

📒 Files selected for processing (8)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (1 hunks)
  • src/main/java/com/example/HelloModel.java (1 hunks)
  • src/main/java/com/example/ImageServer.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/main/java/com/example/NtfyMessageDto.java (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/example/ImageServer.java
  • src/test/java/com/example/HelloModelTest.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/example/NtfyConnectionSpy.java (1)
src/main/java/com/example/HelloModel.java (1)
  • HelloModel (19-185)
src/main/java/com/example/NtfyConnectionImpl.java (1)
src/main/java/com/example/HelloModel.java (1)
  • HelloModel (19-185)
🔇 Additional comments (21)
src/main/java/com/example/NtfyMessageDto.java (1)

1-24: LGTM! Clean DTO structure.

The use of Java records for immutable DTOs is excellent, and the Jackson annotations are properly applied to handle unknown JSON fields gracefully. The structure aligns well with the Ntfy message format.

src/main/java/com/example/HelloFX.java (4)

19-26: Good error handling for missing configuration.

The dotenv validation with early return prevents the application from starting with invalid configuration, providing clear feedback via showError().


28-37: Proper resource management and initialization.

Both the connection and image server are correctly stored in instance fields and will be properly cleaned up in the shutdown hook. The directory creation and error handling are appropriate.


48-49: Connection injection properly implemented.

The controller now correctly receives the connection instance created in HelloFX, ensuring a single shared connection is used throughout the application lifecycle.


55-69: Proper resource cleanup on application shutdown.

The shutdown hook correctly stops both the connection and image server, with independent error handling to ensure one failure doesn't prevent the other from being cleaned up.

src/test/java/com/example/NtfyConnectionSpy.java (2)

10-39: Clean test double implementation.

The spy correctly implements all NtfyConnection methods with simple tracking behavior suitable for testing. The public fields enable test verification without complex assertion APIs.


44-59: Correct time unit conversion for test messages.

The timestamp is properly converted from milliseconds to seconds (line 46), ensuring consistency with production code expectations.

src/main/java/com/example/HelloModel.java (5)

19-40: Well-structured model with proper validation.

The dual constructor approach cleanly separates GUI and headless modes for testing, and the null check prevents invalid construction.


61-76: Proper message sending with optimistic UI update.

The implementation correctly validates input, sends the message, and immediately updates the local message list for responsive UI feedback. Time conversion to seconds is correct.


110-134: Proper message filtering and handling.

The clientId-based filtering correctly prevents echo, and the separate handling paths for image vs. text messages ensure proper UI rendering.


140-148: Thread-safe ObservableList updates.

The implementation correctly ensures all UI updates happen on the JavaFX Application Thread while supporting headless testing.


153-183: Solid HTTP upload implementation with proper validation.

The MIME type check, timeouts, and resource management are well-implemented. The hardcoded localhost:8081 URL is acceptable for this local image server scenario.

src/main/java/com/example/NtfyConnectionImpl.java (4)

13-26: Proper initialization with validation.

The constructor correctly validates the hostname and initializes thread-safe resources. The volatile flag ensures visibility across threads.


45-47: Clear indication of moved functionality.

The explicit exception with a descriptive message clearly communicates that image sending is now handled at the HelloModel layer.


50-84: Polling-based message reception with proper lifecycle management.

The implementation uses polling rather than long-lived HTTP streaming. The daemon thread, error handling with backoff, and proper interrupt handling are all correctly implemented. The 1-second poll interval is reasonable for a chat application.


87-92: Clean thread termination.

The stopReceiving() method correctly signals termination and interrupts the receiver thread, ensuring clean shutdown.

src/main/java/com/example/HelloController.java (5)

32-41: Flexible injection supporting both model and connection.

The dual injection methods support different initialization paths while ensuring attachListeners() is always called.


65-71: Good UX with Enter-to-send and auto-scroll.

The keyboard shortcut and automatic scrolling to show new messages are user-friendly touches.


74-82: Standard message send with proper cleanup.

The implementation correctly validates, sends, and clears the input field.


85-108: Robust file selection with validation and feedback.

The FileChooser configuration, file existence check, and status feedback provide good error handling and user experience.


110-171: Well-implemented message rendering with good UX.

The bubble styling, timestamp formatting, and image enlargement feature create a polished chat interface. The consistent use of Instant.ofEpochSecond() correctly expects timestamps in seconds.

@kappsegla kappsegla closed this Nov 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants